package org.groupg.filetodb.util;

import cn.hutool.core.lang.Assert;
import com.google.common.base.MoreObjects;
import org.apache.commons.lang3.ObjectUtils;
import org.groupg.filetodb.model.ColumnConfig;
import org.groupg.filetodb.model.TableConfig;
import org.groupg.filetodb.model.database.*;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 提供从 DatabaseMetaData 获取数据库元数据并封装到对象中的工具方法。
 */
public class DatabaseMetadataUtil {

    /**
     * 从 DatabaseMetaData 获取所有表的信息，并转换为 TableConfig 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 表对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<Table> loadTables(DatabaseMetaData metaData) throws SQLException {
        List<Table> tables = new ArrayList<>();

        try (ResultSet rs = metaData.getTables(null, null, "%", new String[]{"TABLE"})) {
            while (rs.next()) {
                Table table = new Table();
                table.setTableCat(rs.getString("TABLE_CAT"));     // 目录
                table.setTableSchem(rs.getString("TABLE_SCHEM")); // 模式
                table.setTableName(rs.getString("TABLE_NAME"));   // 表名
                table.setTableType(rs.getString("TABLE_TYPE"));   // 表类型
                table.setRemarks(rs.getString("REMARKS"));        // 注释
                tables.add(table);
            }
        }

        return tables;
    }

    /**
     * 从 DatabaseMetaData 获取所有列的信息，并转换为 ColumnConfig 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 列对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<Column> loadColumns(DatabaseMetaData metaData) throws SQLException {
        List<Column> columns = new ArrayList<>();

        try (ResultSet rs = metaData.getColumns(null, null, "%", "%")) {
            while (rs.next()) {
                Column col = new Column();
                col.setTableCat(rs.getString("TABLE_CAT"));
                col.setTableSchem(rs.getString("TABLE_SCHEM"));
                col.setTableName(rs.getString("TABLE_NAME"));
                col.setColumnName(rs.getString("COLUMN_NAME"));
                col.setDataType(rs.getInt("DATA_TYPE"));
                col.setTypeName(rs.getString("TYPE_NAME"));
                col.setColumnSize(rs.getInt("COLUMN_SIZE"));
                col.setDecimalDigits(rs.getInt("DECIMAL_DIGITS"));
                col.setNumPrecRadix(rs.getInt("NUM_PREC_RADIX"));
                col.setNullable(rs.getInt("NULLABLE"));
                col.setRemarks(rs.getString("REMARKS"));
                col.setColumnDef(rs.getString("COLUMN_DEF"));
                col.setSqlDataType(rs.getInt("SQL_DATA_TYPE"));
                col.setSqlDatetimeSub(rs.getInt("SQL_DATETIME_SUB"));
                col.setCharOctetLength(rs.getInt("CHAR_OCTET_LENGTH"));
                col.setOrdinalPosition(rs.getInt("ORDINAL_POSITION"));
                col.setIsNullable(rs.getString("IS_NULLABLE"));
                col.setIsAutoincrement(rs.getString("IS_AUTOINCREMENT"));

                columns.add(col);
            }
        }

        return columns;
    }

    /**
     * 从 DatabaseMetaData 获取所有函数的信息，并转换为 Function 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 函数对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<Function> loadFunctions(DatabaseMetaData metaData) throws SQLException {
        List<Function> functions = new ArrayList<>();

        try (ResultSet rs = metaData.getFunctions(null, null, "%")) {
            while (rs.next()) {
                Function function = new Function();
                function.setFunctionCat(rs.getString("FUNCTION_CAT"));
                function.setFunctionSchem(rs.getString("FUNCTION_SCHEM"));
                function.setFunctionName(rs.getString("FUNCTION_NAME"));
                function.setRemarks(rs.getString("REMARKS"));
                function.setFunctionType(rs.getShort("FUNCTION_TYPE"));
                function.setSpecificName(rs.getString("SPECIFIC_NAME"));

                functions.add(function);
            }
        }

        return functions;
    }

    /**
     * 从 DatabaseMetaData 获取所有函数参数的信息，并转换为 FunctionColumn 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 函数参数对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<FunctionColumn> loadFunctionColumns(DatabaseMetaData metaData) throws SQLException {
        List<FunctionColumn> columns = new ArrayList<>();

        try (ResultSet rs = metaData.getFunctionColumns(null, null, "%", "%")) {
            while (rs.next()) {
                FunctionColumn col = new FunctionColumn();
                col.setFunctionCat(rs.getString("FUNCTION_CAT"));
                col.setFunctionSchem(rs.getString("FUNCTION_SCHEM"));
                col.setFunctionName(rs.getString("FUNCTION_NAME"));
                col.setColumnName(rs.getString("COLUMN_NAME"));
                col.setColumnType(rs.getShort("COLUMN_TYPE"));
                col.setDataType(rs.getInt("DATA_TYPE"));
                col.setTypeName(rs.getString("TYPE_NAME"));
                col.setPrecision(rs.getInt("PRECISION"));
                col.setLength(rs.getShort("LENGTH"));
                col.setScale(rs.getObject("SCALE", Short.class));
                col.setRadix(rs.getObject("RADIX", Short.class));
                col.setNullable(rs.getObject("NULLABLE", Short.class));
                col.setRemarks(rs.getString("REMARKS"));

                columns.add(col);
            }
        }

        return columns;
    }

    /**
     * 从 DatabaseMetaData 获取所有索引的信息，并转换为 IndexInfo 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 索引对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<IndexInfo> loadIndexInfos(DatabaseMetaData metaData) throws SQLException {
        List<IndexInfo> indexInfos = new ArrayList<>();

        try (ResultSet rs = metaData.getIndexInfo(null, null, "%", false, false)) {
            while (rs.next()) {
                IndexInfo info = new IndexInfo();
                info.setTableCat(rs.getString("TABLE_CAT"));
                info.setTableSchem(rs.getString("TABLE_SCHEM"));
                info.setTableName(rs.getString("TABLE_NAME"));
                info.setNonUnique(rs.getBoolean("NON_UNIQUE"));
                info.setIndexQualifier(rs.getString("INDEX_QUALIFIER"));
                info.setIndexName(rs.getString("INDEX_NAME"));
                info.setType(rs.getShort("TYPE"));
                info.setOrdinalPosition(rs.getShort("ORDINAL_POSITION"));
                info.setColumnName(rs.getString("COLUMN_NAME"));
                info.setAscOrDesc(rs.getString("ASC_OR_DESC"));
                info.setCardinality(rs.getInt("CARDINALITY"));

                indexInfos.add(info);
            }
        }

        return indexInfos;
    }

    /**
     * 从 DatabaseMetaData 获取所有主键的信息，并转换为 PrimaryKey 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 主键对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<PrimaryKey> loadPrimaryKeys(DatabaseMetaData metaData) throws SQLException {
        List<PrimaryKey> primaryKeys = new ArrayList<>();

        try (ResultSet rs = metaData.getPrimaryKeys(null, null, "%")) {
            while (rs.next()) {
                PrimaryKey key = new PrimaryKey();
                key.setTableCat(rs.getString("TABLE_CAT"));
                key.setTableSchem(rs.getString("TABLE_SCHEM"));
                key.setTableName(rs.getString("TABLE_NAME"));
                key.setColumnName(rs.getString("COLUMN_NAME"));
                key.setKeySeq(rs.getShort("KEY_SEQ"));
                key.setPkName(rs.getString("PK_NAME"));

                primaryKeys.add(key);
            }
        }

        return primaryKeys;
    }

    /**
     * 从 DatabaseMetaData 获取所有数据库支持的数据类型信息，并转换为 TypeInfo 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 类型信息对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<TypeInfo> loadTypeInfo(DatabaseMetaData metaData) throws SQLException {
        List<TypeInfo> typeInfos = new ArrayList<>();

        try (ResultSet rs = metaData.getTypeInfo()) {
            while (rs.next()) {
                TypeInfo info = new TypeInfo();
                info.setTypeName(rs.getString("TYPE_NAME"));
                info.setDataType(rs.getInt("DATA_TYPE"));
                info.setPrecision(rs.getInt("PRECISION"));
                info.setLiteralPrefix(rs.getString("LITERAL_PREFIX"));
                info.setLiteralSuffix(rs.getString("LITERAL_SUFFIX"));
                info.setCreateParams(rs.getString("CREATE_PARAMS"));
                info.setNullable(rs.getInt("NULLABLE"));
                info.setCaseSensitive(rs.getBoolean("CASE_SENSITIVE"));
                info.setSearchable(rs.getShort("SEARCHABLE"));
                info.setUnsigned(rs.getBoolean("UNSIGNED_ATTRIBUTE"));
                info.setFixedPrecScale(rs.getBoolean("FIXED_PREC_SCALE"));
                info.setAutoIncrement(rs.getBoolean("AUTO_INCREMENT"));
                info.setLocalTypeName(rs.getString("LOCAL_TYPE_NAME"));
                info.setMinScale(rs.getShort("MINIMUM_SCALE"));
                info.setMaxScale(rs.getShort("MAXIMUM_SCALE"));
                info.setDataType(rs.getInt("SQL_DATA_TYPE"));
                info.setNumPrecRadix(rs.getInt("NUM_PREC_RADIX"));

                typeInfos.add(info);
            }
        }

        return typeInfos;
    }

    /**
     * 从 DatabaseMetaData 获取所有存储过程的信息，并转换为 Procedure 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 存储过程对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<Procedure> loadProcedures(DatabaseMetaData metaData) throws SQLException {
        List<Procedure> procedures = new ArrayList<>();

        try (ResultSet rs = metaData.getProcedures(null, null, "%")) {
            while (rs.next()) {
                Procedure proc = new Procedure();
                proc.setProcedureCat(rs.getString("PROCEDURE_CAT"));
                proc.setProcedureSchem(rs.getString("PROCEDURE_SCHEM"));
                proc.setProcedureName(rs.getString("PROCEDURE_NAME"));
                proc.setRemarks(rs.getString("REMARKS"));
                proc.setProcedureType(rs.getShort("PROCEDURE_TYPE"));
                proc.setSpecificName(rs.getString("SPECIFIC_NAME"));

                procedures.add(proc);
            }
        }

        return procedures;
    }

    /**
     * 从 DatabaseMetaData 获取所有存储过程参数的信息，并转换为 ProcedureColumn 对象列表。
     *
     * @param metaData 数据库元数据对象
     * @return 存储过程参数对象列表
     * @throws SQLException 如果数据库访问出错
     */
    public static List<ProcedureColumn> loadProcedureColumns(DatabaseMetaData metaData) throws SQLException {
        List<ProcedureColumn> columns = new ArrayList<>();

        try (ResultSet rs = metaData.getProcedureColumns(null, null, "%", "%")) {
            while (rs.next()) {
                ProcedureColumn col = new ProcedureColumn();
                col.setProcedureCat(rs.getString("PROCEDURE_CAT"));
                col.setProcedureSchem(rs.getString("PROCEDURE_SCHEM"));
                col.setProcedureName(rs.getString("PROCEDURE_NAME"));
                col.setColumnName(rs.getString("COLUMN_NAME"));
                col.setColumnType(rs.getShort("COLUMN_TYPE"));
                col.setDataType(rs.getInt("DATA_TYPE"));
                col.setTypeName(rs.getString("TYPE_NAME"));
                col.setPrecision(rs.getInt("PRECISION"));
                col.setLength(rs.getShort("LENGTH"));
                col.setScale(rs.getShort("SCALE"));
                col.setRadix(rs.getObject("RADIX", Short.class));
                col.setNullable(rs.getObject("NULLABLE", Short.class));
                col.setRemarks(rs.getString("REMARKS"));

                columns.add(col);
            }
        }

        return columns;
    }

    /**
     * 加载数据库元数据信息。
     *
     * @param metaData 数据库元数据对象
     * @return 包含全部元数据信息的对象
     * @throws SQLException 如果数据库访问出错
     */
    public static DatabaseMetadataInfo loadDatabaseMetadataInfo(DatabaseMetaData metaData) throws SQLException {
        DatabaseMetadataInfo dbInfo = new DatabaseMetadataInfo();

        // Load Tables
        dbInfo.setTables(loadTables(metaData));

        // Load Columns
        dbInfo.setColumns(loadColumns(metaData));

        // Load Primary Keys
        dbInfo.setPrimaryKeys(loadPrimaryKeys(metaData));

        // Load Type Info
        dbInfo.setTypeInfos(loadTypeInfo(metaData));

        // Load Index Info
        dbInfo.setIndexInfos(loadIndexInfos(metaData));

        // Load Functions
        dbInfo.setFunctions(loadFunctions(metaData));

        // Load Function Columns
        dbInfo.setFunctionColumns(loadFunctionColumns(metaData));

        return dbInfo;
    }

    /**
     * 根据表配置获取数据库表元信息
     * @param metadataInfo 数据库元信息
     * @param tableConfig 表配置
     * @return 表源信息
     */
    public static Table getTableInfo(DatabaseMetadataInfo metadataInfo, TableConfig tableConfig) {
        Assert.notNull(tableConfig, "表配置不能为空");
        String tableName = tableConfig.getTableName();
        List<Table> tables = metadataInfo.getTables();
        AtomicReference<Table> retTable = new AtomicReference<>(new Table());
        tables.stream().filter(table -> Objects.equals(table.getTableName().toLowerCase(), tableName.toLowerCase())).findFirst().ifPresent(retTable::set);
        return retTable.get();
    }
    /**
     * 根据字段配置获取数据库字段元信息
     * @param metadataInfo 数据库元信息
     * @param columnConfig 字段配置
     * @return 字段源信息
     */
    public static Column getColumnInfo(DatabaseMetadataInfo metadataInfo, ColumnConfig columnConfig) {
        Assert.notNull(columnConfig, "表配置不能为空");
        String tableName = columnConfig.getTableName();
        String columnName = columnConfig.getColumnName();
        List<Column> columns = metadataInfo.getColumns();
        AtomicReference<Column> ret = new AtomicReference<>(new Column());
        columns.stream().filter(c-> Objects.equals(c.getColumnName().toLowerCase(), columnName.toLowerCase()) && Objects.equals(c.getTableName().toLowerCase(), tableName.toLowerCase())).findFirst().ifPresent(ret::set);
        return ret.get();
    }
}